<template>
    <view :class="`wd-picker-view ${customClass}`" :style="customStyle">
        <view class="wd-picker-view__loading" v-if="loading">
            <wd-loading :color="loadingColor" />
        </view>
        <view :style="`height: ${columnsHeight - 20}px;`">
            <picker-view
                mask-class="wd-picker-view__mask"
                indicator-class="wd-picker-view__roller"
                :indicator-style="`height: ${itemHeight}px;`"
                :style="`height: ${columnsHeight - 20}px;`"
                :value="selectedIndex"
                :immediate-change="immediateChange"
                @change="onChange"
                @pickstart="onPickStart"
                @pickend="onPickEnd"
            >
                <picker-view-column
                    v-for="(col, colIndex) in formatColumns"
                    :key="colIndex"
                    class="wd-picker-view-column"
                >
                    <view
                        v-for="(row, rowIndex) in col"
                        :key="rowIndex"
                        :class="`wd-picker-view-column__item ${row['disabled'] ? 'wd-picker-view-column__item--disabled' : ''}  ${
                            selectedIndex[colIndex] == rowIndex
                                ? 'wd-picker-view-column__item--active'
                                : ''
                        }`"
                        :style="`line-height: ${itemHeight}px;`"
                    >
                        {{ row[labelKey] }}
                    </view>
                </picker-view-column>
            </picker-view>
        </view>
    </view>
</template>

<script lang="ts">
export default {
    name: 'wd-picker-view',
    options: {
        virtualHost: true,
        addGlobalClass: true,
        styleIsolation: 'shared',
    },
}
</script>
<script lang="ts" setup>
import wdLoading from '../wd-loading/wd-loading.vue'
import { getCurrentInstance, ref, watch, nextTick } from 'vue'
import {
    deepClone,
    getType,
    isArray,
    isDef,
    isEqual,
    range,
} from '../common/util'
import {
    formatArray,
    pickerViewProps,
    type ColumnItem,
    type PickerViewExpose,
} from './types'

const props = defineProps(pickerViewProps)
const emit = defineEmits([
    'change',
    'pickstart',
    'pickend',
    'update:modelValue',
])

const formatColumns = ref<ColumnItem[][]>([]) // 格式化后的列数据
const itemHeight = ref<number>(35)
const selectedIndex = ref<Array<number>>([]) // 格式化之后，每列选中的下标集合

watch(
    [() => props.modelValue, () => props.columns],
    (newValue, oldValue) => {
        if (!isEqual(oldValue[1], newValue[1])) {
            if (isArray(newValue[1]) && newValue[1].length > 0) {
                formatColumns.value = formatArray(
                    newValue[1],
                    props.valueKey,
                    props.labelKey,
                )
            } else {
                // 当 columns 变为空时，清空 formatColumns 和 selectedIndex
                formatColumns.value = []
                selectedIndex.value = []
            }
        }
        if (isDef(newValue[0])) {
            selectWithValue(newValue[0])
        }
    },
    {
        deep: true,
        immediate: true,
    },
)

const { proxy } = getCurrentInstance() as any

/**
 * 根据传入的value，寻找对应的索引，并传递给原生选择器。
 * 需要保证formatColumns先设置，之后会修改selectedIndex。
 * @param {String|Number|Boolean|Array<String|Number|Boolean|Array<any>>}value
 */
function selectWithValue(
    value: string | number | boolean | number[] | string[] | boolean[],
) {
    if (formatColumns.value.length === 0) {
        selectedIndex.value = [] // 如果列为空，直接清空选中索引
        return
    }
    // 使其默认选中首项
    if (
        value === '' ||
        !isDef(value) ||
        (isArray(value) && value.length === 0)
    ) {
        value = formatColumns.value.map((col) => {
            return col[0][props.valueKey]
        })
    }
    const valueType = getType(value)
    const type = ['string', 'number', 'boolean', 'array']
    if (type.indexOf(valueType) === -1)
        console.error(`value must be one of ${type.toString()}`)

    /**
     * 1.单key转为Array<key>
     * 2.根据formatColumns的长度截取Array<String>，保证下面的遍历不溢出
     * 3.根据每列的key值找到选项中value为此key的下标并记录
     */
    value = isArray(value) ? value : [value as string]
    value = value.slice(0, formatColumns.value.length)

    let selected: number[] = deepClone(selectedIndex.value)
    value.forEach((target, col) => {
        let row = formatColumns.value[col].findIndex((row) => {
            return row[props.valueKey].toString() === target.toString()
        })
        row = row === -1 ? 0 : row
        selected = correctSelectedIndex(col, row, selected)
    })
    /** 根据formatColumns的长度去除selectWithIndex无用的部分。
     * 始终保持value、selectWithIndex、formatColumns长度一致
     */
    selectedIndex.value = selected.slice(0, value.length)
}

/**
 * 修正选中项的值
 * @param value 当前picker选择器选中的值
 * @param origin 原始选中的值
 */
function correctSelected(value: number[]) {
    let selected = deepClone(value)
    value.forEach((row, col) => {
        row = range(row, 0, formatColumns.value[col].length - 1)
        selected = correctSelectedIndex(col, row, selected)
    })
    return selected
}

/**
 * 修正选中项指定列行的值
 * @param columnIndex 列下标
 * @param rowIndex 行下标
 * @param selected 选中值列表
 */
function correctSelectedIndex(
    columnIndex: number,
    rowIndex: number,
    selected: number[],
) {
    const col = formatColumns.value[columnIndex]
    if (!col || !col[rowIndex]) {
        throw Error(
            `The value to select with Col:${columnIndex} Row:${rowIndex} is incorrect`,
        )
    }
    const select: number[] = deepClone(selected)
    select[columnIndex] = rowIndex

    // 被禁用的无法选中，选中距离它最近的未被禁用的
    if (col[rowIndex].disabled) {
        // 寻找值为0或最最近的未被禁用的节点的索引
        const prev = col
            .slice(0, rowIndex)
            .reverse()
            .findIndex((s) => !s.disabled)
        const next = col.slice(rowIndex + 1).findIndex((s) => !s.disabled)
        if (prev !== -1) {
            select[columnIndex] = rowIndex - 1 - prev
        } else if (next !== -1) {
            select[columnIndex] = rowIndex + 1 + next
        } else if (select[columnIndex] === undefined) {
            select[columnIndex] = 0
        }
    }
    return select
}

/**
 * 选择器选中项变化时触发
 * @param param0
 */
function onChange({ detail: { value } }: { detail: { value: number[] } }) {
    value = value.map((v: any) => {
        return Number(v || 0)
    })
    const index = getChangeDiff(value)
    // 先将picker选择器的值赋给selectedIndex，然后重新赋予修正后的值，防止两次操作修正结果一致时pikcer视图不刷新
    selectedIndex.value = deepClone(value)
    nextTick(() => {
        // 重新赋予修正后的值
        selectedIndex.value = correctSelected(value)
        if (props.columnChange) {
            // columnsChange 可能有异步操作，需要添加 resolve 进行回调通知，形参小于4个则为同步
            if (props.columnChange.length < 4) {
                props.columnChange(
                    proxy.$.exposed,
                    getSelects(),
                    index || 0,
                    () => {},
                )
                handleChange(index || 0)
            } else {
                props.columnChange(
                    proxy.$.exposed,
                    getSelects(),
                    index || 0,
                    () => {
                        // 如果selectedIndex只有一列，返回此项；如果是多项，返回所有选中项。
                        handleChange(index || 0)
                    },
                )
            }
        } else {
            // 如果selectedIndex只有一列，返回此项；如果是多项，返回所有选中项。
            handleChange(index || 0)
        }
    })
}

/**
 * 获取选中项变化的列的下标
 * @param now 当前选中项值
 * @param origin 旧选中项值
 */
function getChangeColumn(now: number[], origin: number[]) {
    if (!now || !origin) return -1
    const index = now.findIndex((row, index) => row !== origin[index])
    return index
}

function getChangeDiff(value: number[]) {
    value = value.slice(0, formatColumns.value.length)

    // 保留选中前的
    const origin: number[] = deepClone(selectedIndex.value)
    // 存储赋值旧值，便于外部比较
    let selected: number[] = deepClone(selectedIndex.value)

    value.forEach((row, col) => {
        row = range(row, 0, formatColumns.value[col].length - 1)
        if (row === origin[col]) return
        selected = correctSelectedIndex(col, row, selected)
    })

    // 值变化的列
    const diffCol = getChangeColumn(selected, origin)
    if (diffCol === -1) return

    // 获取变化的的行
    const diffRow = selected[diffCol]

    // 如果selectedIndex只有一列，返回选中项的索引；如果是多项，返回选中项所在的列。
    return selected.length === 1 ? diffRow : diffCol
}

/**
 * 列更新
 * @param index 列下标
 */
function handleChange(index: number) {
    const value = getValues()

    // 避免多次触发change
    if (isEqual(value, props.modelValue)) return

    emit('update:modelValue', value)
    // 延迟一下，避免组件刚渲染时调用者的事件未初始化好
    setTimeout(() => {
        emit('change', {
            picker: proxy.$.exposed,
            value,
            index,
        })
    }, 0)
}

/**
 * @description 获取所有列选中项，返回值为一个数组
 */
function getSelects() {
    const selects = selectedIndex.value.map(
        (row, col) => formatColumns.value[col][row],
    )
    // 单列选择器，则返回单项
    if (selects.length === 1) {
        return selects[0]
    }
    return selects
}

/**
 * 获取所有列的选中值
 * 如果values只有一项则将第一项返回
 */
function getValues() {
    const { valueKey } = props
    const values = selectedIndex.value.map((row, col) => {
        return formatColumns.value[col][row][valueKey]
    })

    if (values.length === 1) {
        return values[0]
    }
    return values
}

/**
 * 获取所有列选中项的label，返回值为一个数组
 */
function getLabels() {
    const { labelKey } = props
    return selectedIndex.value.map(
        (row, col) => formatColumns.value[col][row][labelKey],
    )
}

/**
 * 获取某一列的选中项下标
 * @param {Number} columnIndex 列的下标
 * @returns {Number} 下标
 */
function getColumnIndex(columnIndex: number) {
    return selectedIndex.value[columnIndex]
}

/**
 *  获取某一列的选项
 * @param {Number} columnIndex 列的下标
 * @returns {Array<{valueKey,labelKey}>} 当前列的集合
 */
function getColumnData(columnIndex: number) {
    return formatColumns.value[columnIndex]
}

/**
 * 设置列数据
 * @param columnIndex 列下标
 * @param data // 列数据
 * @param rowIndex // 行下标
 */
function setColumnData(
    columnIndex: number,
    data: Array<
        string | number | ColumnItem | Array<string | number | ColumnItem>
    >,
    rowIndex: number = 0,
) {
    formatColumns.value[columnIndex] = formatArray(
        data,
        props.valueKey,
        props.labelKey,
    ).reduce((acc, val) => acc.concat(val), [])
    selectedIndex.value = correctSelectedIndex(
        columnIndex,
        rowIndex,
        selectedIndex.value,
    )
}

/**
 * 获取列数据
 */
function getColumnsData() {
    return deepClone(formatColumns.value)
}

/**
 * 获取选中数据
 */
function getSelectedIndex() {
    return selectedIndex.value
}

/**
 * 用于重置列数据为指定列数据
 */
function resetColumns(
    columns: (
        | string
        | number
        | string[]
        | number[]
        | ColumnItem
        | ColumnItem[]
    )[],
) {
    if (isArray(columns) && columns.length) {
        formatColumns.value = formatArray(
            columns,
            props.valueKey,
            props.labelKey,
        )
    }
}

function onPickStart() {
    emit('pickstart')
}

function onPickEnd() {
    emit('pickend')
}

defineExpose<PickerViewExpose>({
    getSelects,
    getValues,
    setColumnData,
    getColumnsData,
    getColumnData,
    getColumnIndex,
    getLabels,
    getSelectedIndex,
    resetColumns,
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
